﻿using System;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using BusinessIntelligenceToolkit.Controls.ContainerGenerators;

namespace BusinessIntelligenceToolkit.Controls
{
    public class BitStackPanel : Panel
    {
        public static readonly DependencyProperty OrientationProperty =
            DependencyProperty.Register("Orientation", typeof(Orientation), typeof(BitStackPanel), new PropertyMetadata(Orientation.Vertical));

        public static readonly DependencyProperty ContainerGeneratorProperty =
            DependencyProperty.Register("ContainerGenerator", typeof(IContainerGenerator), typeof(BitStackPanel), new PropertyMetadata(default(IContainerGenerator), OnContainerGeneratorChanged, OnCoerceContainerGenerator));

        public static readonly DependencyProperty OffsetProperty =
            DependencyProperty.Register(
            "Offset",
            typeof(double),
            typeof(BitStackPanel),
            new FrameworkPropertyMetadata(default(double), FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure));

        public static readonly DependencyProperty CollectionViewProperty =
            DependencyProperty.Register(
            "CollectionView",
            typeof(ListCollectionView),
            typeof(BitStackPanel),
            new FrameworkPropertyMetadata(default(ICollectionView), FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure, OnCollectionViewChanged, OnCoerceCollectionView));

        public static readonly DependencyProperty ScrollViewerProperty =
            DependencyProperty.Register("ScrollViewer", typeof(BitScrollViewer), typeof(BitStackPanel), new PropertyMetadata(default(BitScrollViewer)));

        private ListCollectionView _listCollectionView;
        private int _minimumLogicalNegativeOffset = -5;
        private int _maximumLogicalPositiveOffset = 5;

        public Orientation Orientation
        {
            get { return (Orientation)GetValue(OrientationProperty); }
            set { SetValue(OrientationProperty, value); }
        }

        public double Offset
        {
            get { return (double)GetValue(OffsetProperty); }
            set { SetValue(OffsetProperty, value); }
        }

        public IContainerGenerator ContainerGenerator
        {
            get { return (IContainerGenerator)GetValue(ContainerGeneratorProperty); }
            set { SetValue(ContainerGeneratorProperty, value); }
        }

        public ListCollectionView CollectionView
        {
            get { return (ListCollectionView)GetValue(CollectionViewProperty); }
            set { SetValue(CollectionViewProperty, value); }
        }

        public BitScrollViewer ScrollViewer
        {
            get { return (BitScrollViewer)GetValue(ScrollViewerProperty); }
            set { SetValue(ScrollViewerProperty, value); }
        }
        
        public bool IsScrolling
        {
            get
            {
                return ScrollViewer.IsScrolling;
            }
        }

        public int ContainersInViewport { get; set; }

        public bool Raised { get; set; }

        protected override Size MeasureOverride(Size size)
        {
            var desiredSize = MeasureVertically(size);
            return desiredSize;
        }

        protected override Size ArrangeOverride(Size arrangeSize)
        {
            var desiredSize = ArrangeVertically(arrangeSize);
            return desiredSize;
        }

        protected Size MeasureVertically(Size size)
        {
            if (CollectionView == null || CollectionView.Count == 0)
            {
                return size;
            }

            if (ScrollViewer.IsHeightChanged)
            {
                double pvY = size.Height;
                var actLogIndex = CollectionView.Count - 1;
                var actViewportY = Children.Count - 1;
                ContainersInViewport = 0;
                while (pvY >= 0 && actViewportY > 0)
                {
                    UIElement container = GetContainerFromViewport(actViewportY, actLogIndex);
                    actViewportY--;
                    actLogIndex--;
                    ContainersInViewport++;
                    container.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
                    pvY -= container.DesiredSize.Height;
                }

                return new Size(Children.Count > 0 ? Children.Cast<UIElement>().Max(x => x.DesiredSize.Width) : size.Width, size.Height);
            }

            // initializes actual offset
            double actualOffset;
            if (Offset < _minimumLogicalNegativeOffset)
            {
                actualOffset = _minimumLogicalNegativeOffset;
            }
            else if (Offset > CollectionView.Count + _maximumLogicalPositiveOffset)
            {
                actualOffset = CollectionView.Count + _maximumLogicalPositiveOffset;
            }
            else
            {
                actualOffset = Offset;
            }

            int logicalStartIndex = (int)Math.Floor(Offset);

            int firstItemIndex = logicalStartIndex < 0 || logicalStartIndex >= CollectionView.Count ? 0 : logicalStartIndex;
            UIElement firstChild = GetContainerFromViewport(0, firstItemIndex);

            Size defaultSize = GetDefaultSize();

            // initializes physicalViewportY
            double physicalOffset = (Offset - Math.Floor(Offset)) * defaultSize.Height;
            double physicalViewportY = 0.0;
            if (actualOffset < 0 || actualOffset > CollectionView.Count)
            {
                physicalViewportY = defaultSize.Height - physicalOffset;
            }
            else
            {
                physicalViewportY = firstChild.DesiredSize.Height - physicalOffset;
            }

            // lazy children adding
            int actualLogicalIndex = firstItemIndex + 1;
            var actualViewportIndex = 1;

            UIElement lastContainer = firstChild;
            ContainersInViewport = 0;
            if (Math.Floor(physicalViewportY) > 0)
            {
                ContainersInViewport++;
            }

            while (physicalViewportY < size.Height && actualLogicalIndex < CollectionView.Count)
            {
                lastContainer = GetContainerFromViewport(actualViewportIndex, actualLogicalIndex);
                actualViewportIndex++;
                actualLogicalIndex++;
                lastContainer.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
                physicalViewportY += lastContainer.DesiredSize.Height;
                if (physicalViewportY >= 0)
                {
                    ContainersInViewport++;
                }
            }

            ClearChildrenToReuse(actualViewportIndex);

            if (ScrollViewer.VerticalScrollBar.Maximum == 0)
            {
                ScrollViewer.VerticalScrollBar.Maximum = CollectionView.Count - actualViewportIndex;
            }

            if (actualLogicalIndex == CollectionView.Count
                && Math.Floor(physicalViewportY) > size.Height
                && ScrollViewer.VerticalScrollBar.Maximum != CollectionView.Count - ContainersInViewport + 1)
            {
                ScrollViewer.VerticalScrollBar.Maximum = CollectionView.Count - ContainersInViewport + 1;
            }

            return new Size(Children.Count > 0 ? Children.Cast<UIElement>().Max(x => x.DesiredSize.Width) : size.Width, size.Height);
        }

        protected Size ArrangeVertically(Size size)
        {
            if (CollectionView == null || CollectionView.Count == 0)
            {
                return size;
            }

            double offset = Offset > 0 ? (Offset - (int)Math.Floor(Offset)) : Offset;

            if (ScrollViewer.IsHeightChanged)
            {
                var unclearChildren = Children;
                double pvY = size.Height;
                int currentItemIndex = unclearChildren.Count - 1;
                while (currentItemIndex >= 0)
                {
                    pvY -= unclearChildren[currentItemIndex].DesiredSize.Height;
                    var rect = new Rect(
                                        0,
                                        pvY,
                                        Math.Max(size.Width, unclearChildren[currentItemIndex].DesiredSize.Width),
                                        unclearChildren[currentItemIndex].DesiredSize.Height);
                    unclearChildren[currentItemIndex].Arrange(rect);
                    currentItemIndex--;
                }

                return size;
            }

            Size defaultSize = GetDefaultSize();

            // initializes physicalViewportY
            double physicalOffset = -1 * offset * defaultSize.Height;
            double physicalViewportY = 0.0;
            Children[0].Arrange(new Rect(0, physicalViewportY + physicalOffset, Math.Max(size.Width, Children[0].DesiredSize.Width), Children[0].DesiredSize.Height));
            physicalViewportY += Children[0].DesiredSize.Height + physicalOffset;

            for (int i = 1; i < Children.Count; i++)
            {
                Children[i].Arrange(new Rect(0, physicalViewportY, Math.Max(size.Width, Children[i].DesiredSize.Width), Children[i].DesiredSize.Height));
                physicalViewportY += Children[i].DesiredSize.Height;
            }

            return size;
        }

        private static void OnCollectionViewChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            BitStackPanel instance = d as BitStackPanel;
            instance._listCollectionView = instance.CollectionView as ListCollectionView;
        }

        private static object OnCoerceCollectionView(DependencyObject d, object basevalue)
        {
            BitStackPanel instance = d as BitStackPanel;
            if (ContainerGeneratorProperty == null)
            {
                return instance.CollectionView;
            }

            return basevalue;
        }

        private static void OnContainerGeneratorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            BitStackPanel instance = d as BitStackPanel;
            instance.CoerceValue(CollectionViewProperty);
        }

        private static object OnCoerceContainerGenerator(DependencyObject d, object basevalue)
        {
            return basevalue;
        }

        private void ClearChildrenToReuse(int from)
        {
            // clear containers to reuse
            for (int i = from; i < Children.Count; i++)
            {
                ContainerGenerator.ClearContainer(Children[i]);
            }
        }

        private Size GetDefaultSize()
        {
            int logicalStartIndex = (int)Math.Floor(Offset);

            int firstItemIndex = logicalStartIndex < 0 || logicalStartIndex >= CollectionView.Count ? 0 : logicalStartIndex;

            // calculate size of the first child
            // it will be considered as default item size
            UIElement firstChild = GetContainerFromViewport(0, firstItemIndex);
            firstChild.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
            Size defaultSize = firstChild.DesiredSize;
            return defaultSize;
        }

        private UIElement GetContainerFromViewport(int viewportIndex, int logicalIndex)
        {
            if (viewportIndex >= Children.Count)
            {
                for (int i = Children.Count; i <= viewportIndex; i++)
                {
                    UIElement container = ContainerGenerator.CreateContainer();
                    Children.Add(container);
                    object data = _listCollectionView.GetItemAt(logicalIndex);
                    ContainerGenerator.PrepareContainerForItem(container, data);
                }
            }
            else if (viewportIndex < 0)
            {
                object data = _listCollectionView.GetItemAt(logicalIndex);
                UIElement container = ContainerGenerator.CreateContainer();
                Children.Insert(0, container);
                ContainerGenerator.PrepareContainerForItem(container, data);
                return container;
            }
            else
            {
                object data = _listCollectionView.GetItemAt(logicalIndex);
                ContainerGenerator.PrepareContainerForItem(Children[viewportIndex], data);
            }

            return Children[viewportIndex];
        }
    }
}